#ifndef __FRONTCODING_MANAGER_H__
#define __FRONTCODING_MANAGER_H__


//TODO hacer que front coding comprima con gamma repeted letter, diferent letters y file pointer

#include "wordcontainer.h"

namespace invertedindex{

class FrontCodingManager{
	private:
		static const unsigned int NOFILEPOINTER=-1;
	private:
		//unsigned int baseLength;
		//char* base;
		WordContainer* base;
		typedef std::set<WordContainer*,WordOrder> Words;
		Words wrds; 

	public:
		FrontCodingManager(const WordContainer& base,unsigned int wordCount ,char* frontCoding){
			//this->baseLength=baseLength;
			//base=new char[baseLength];
			//memcpy(this->base,base,baseLength);
			this->base = new WordContainer(base);	

			if(wordCount>0){
				std::pair<unsigned int, char*> wrdBase=base.getWord();
	
				//desacer frontcoding y armar vector
				unsigned short int numberOfDifferentLetters;
				unsigned short int numberOfRepetedLetters;
				unsigned int idKey;
				unsigned int wordLength;
				char* newWord;
				WordContainer* wc;			
	

				BlockPointer blockPointerAux(0,0);

				char* temp1 = frontCoding;
				char* temp2 = frontCoding+wordCount*(2*sizeof(unsigned short int)+sizeof(unsigned int)+ blockPointerAux.getSize());
				char* temp3;
	
				char* lastWord = wrdBase.second;
	
				unsigned int i;
				for(i=0; i< wordCount; i++){
					memcpy(&numberOfDifferentLetters, temp1, sizeof(unsigned short int));
					temp1+=sizeof(unsigned short int);
	
					memcpy(&numberOfRepetedLetters, temp1, sizeof(unsigned short int));
					temp1+=sizeof(unsigned short int);
	
					memcpy(&idKey, temp1, sizeof(unsigned int));
					temp1+=sizeof(unsigned int);
	
					BlockPointer blockPointer(0,0);
					temp1=blockPointer.toFilePointer(temp1);
	
					wordLength=numberOfDifferentLetters+numberOfRepetedLetters;
					newWord=new char[wordLength];
					
					memcpy(newWord, lastWord, numberOfRepetedLetters);
					temp3=newWord+numberOfRepetedLetters;
					memcpy(temp3, temp2, numberOfDifferentLetters);
					temp2+=numberOfDifferentLetters;
					delete[] lastWord;
					lastWord=newWord;
	
					wc=new WordContainer(wordLength, newWord, idKey);
					if(blockPointer.getBlock()!=NOFILEPOINTER)
						wc->setBP(&blockPointer);
					else
						wc->setBP(NULL);
	
					wrds.insert(wc);
					
				}
				delete[] lastWord;
			}
		}

		~FrontCodingManager(){
			for(Words::iterator it=wrds.begin();it!=wrds.end();++it){
				delete(*it);
			}
			delete base;
			wrds.clear();
		}

		std::pair<unsigned int, char*> getFrontCoding()const{
			std::pair<unsigned int, char*> ret(0, NULL);

			Words::iterator it = wrds.begin();
			//Vector whith Lexico string
			typedef std::vector<std::pair<unsigned int,char*> > FCChars;
			FCChars fchrs;
			//Vector whith Lexico data
			typedef std::vector<char*> FCDatas;
			FCDatas fcdts;

			std::pair<unsigned int, char*> wrdBase=base->getWord();

			BlockPointer blockPointerAux(0,0);

			char* lastWord=wrdBase.second;
			unsigned int lastWordLength=wrdBase.first;

			unsigned short int numberOfDifferentLetters;
			unsigned short int numberOfRepetedLetters;
			unsigned int idKey;

			unsigned int totalLength=0;

			while(it!=wrds.end()){
				//Busco coincidencias entre las letras de la palabra nueva y la vieja
				std::pair<unsigned int,char*> newWrd = (*it)->getWord();
				unsigned int i=0;
				bool find=false;
				while((i<lastWordLength)&&(i<newWrd.first)&&(!find)){
					if(lastWord[i]==(newWrd.second)[i])
						i++;
					else
						find=true;
				}

				//Cargo variables con datos de la nueva palabra
				numberOfRepetedLetters=i;
				numberOfDifferentLetters=newWrd.first-i;
				idKey=(*it)->getWordId();
				delete[] lastWord;
				lastWord=newWrd.second;
				lastWordLength=newWrd.first;

				//Guardo datos palabra en vector datos
				char* savedData;
				savedData=new char[2*sizeof(unsigned short int)+sizeof(unsigned int)+blockPointerAux.getSize()];
				char* temp = savedData;
				memcpy(temp,&numberOfDifferentLetters, sizeof(unsigned short int));
				temp+=sizeof(unsigned short int);
				memcpy(temp, &numberOfRepetedLetters, sizeof(unsigned short int));
				temp+=sizeof(unsigned short int);
				memcpy(temp, &idKey, sizeof(unsigned int));
				temp+=sizeof(unsigned int);

				BlockPointer* blocPoint=(*it)->getBP();
				if(blocPoint==NULL)
					blocPoint=new BlockPointer(NOFILEPOINTER,NOFILEPOINTER);
				temp=blocPoint->toBytes(temp);
				delete blocPoint;


				fcdts.insert(fcdts.end(),savedData);

				//Guardo la palabra recortada en vector lexico
				std::pair<unsigned int,char*> savedLexico(0,NULL);
				savedLexico.second=new char[numberOfDifferentLetters];
				memcpy(savedLexico.second, newWrd.second+numberOfRepetedLetters, numberOfDifferentLetters);
				savedLexico.first=numberOfDifferentLetters;
				fchrs.insert(fchrs.end(),savedLexico);
				
				totalLength=totalLength+2*sizeof(unsigned short int)+sizeof(unsigned int)+blockPointerAux.getSize()+numberOfDifferentLetters;
				it++;
			}
			delete[] lastWord;

			//Armo string final con los datos seguidos de las palabras recortadas
			FCChars::iterator itfcc = fchrs.begin();
			FCDatas::iterator itfcd = fcdts.begin();
			char* finalChar=new char[totalLength];
			char* finalCharTemp=finalChar;
			while(itfcd!=fcdts.end()){
				memcpy(finalCharTemp, (*itfcd), (2*sizeof(unsigned short int)+sizeof(unsigned int)+blockPointerAux.getSize()));
				finalCharTemp+=2*sizeof(unsigned short int)+sizeof(unsigned int)+blockPointerAux.getSize();
				itfcd++;
			}

			while(itfcc!=fchrs.end()){
				std::pair<unsigned int,char*> wordlexico(0,NULL);
				wordlexico=(*itfcc);
				memcpy(finalCharTemp, wordlexico.second, wordlexico.first);
				finalCharTemp+=wordlexico.first;
				itfcc++;
			}

			for(FCChars::iterator its1=fchrs.begin();its1!=fchrs.end();++its1){
				delete[] ((*its1).second);
			}
			fchrs.clear();

			for(FCDatas::iterator its2=fcdts.begin();its2!=fcdts.end();++its2){
				delete[] (*its2);
			}
			fcdts.clear();

			ret.first=totalLength;
			ret.second=finalChar;
			return ret;
		}

		bool searchWord(WordContainer& wc)const{
			bool ret =false;
			if(wc.comparate(*base)==0){
				ret=true;
				wc.setFields(*base);
			}
			else{
				Words::iterator it = wrds.find(&wc);
				if(it!=wrds.end()){
					wc.setFields(*(*it));
				}
				ret=(it!=wrds.end());
			}
			return ret;
		}

		unsigned int getWordCount()const{
			return wrds.size();
		}

		bool insertWord(const WordContainer& wc){
			bool ret = false;
			WordContainer* temp = dynamic_cast<WordContainer*>(wc.duplicate());
			int comparate = wc.comparate(*base) ;
			if(comparate==0){
				ret=false;
				delete temp;
			}
			else{
				if(comparate<0){
					WordContainer* aux= base;
					base=temp;
					temp=aux;	
				}
				std::pair<Words::iterator,bool> p = wrds.insert(temp);
				if(!(p.second)) delete temp;
				ret = p.second;
			}
			return ret;
		}

		bool removeWord(WordContainer& wc){
			bool ret = false;
			int comparate = wc.comparate(*base) ;
			if(comparate==0){
				delete base;
				Words::iterator it = wrds.begin();
				if(it!=wrds.end()){
					base= *it;
					wrds.erase(it);
					ret=true;
				}
			}
			else{
				Words::iterator it = wrds.find(&wc);
				if(it!=wrds.end()){
					delete (*it);
					wrds.erase(it);
					ret= true;
				}
			}
			return ret;
		}

		WordContainer* getBase()const{
			return dynamic_cast<WordContainer*>(base->duplicate());
		}

		unsigned int getWordsLenght(){
			//calculo longitud total del lexico mas base
			Words::iterator it = wrds.begin();
			unsigned int length=base->getWordLength();
			while(it!=wrds.end()){
				length+=(*it)->getWordLength();
				it++;
			}
			return length;
		}

		FrontCodingManager* divide(){
			FrontCodingManager* fcm2=NULL;
			//Si hay mas de 1 registro
			if(wrds.size()>0){
				Words wrdsAux; 

				Words::iterator it = wrds.begin();
				while(it!=wrds.end()){
					wrdsAux.insert(*it);
					it++;
				}
				wrds.clear();

				//calculo longitud total del lexico mas base
				it = wrdsAux.begin();
				unsigned int length=base->getWordLength();
				while(it!=wrdsAux.end()){
					length+=(*it)->getWordLength();
					it++;
				}

				//busco la mitad y a partir de ese lugar creo nuevo front coding
				Words::iterator it2=wrdsAux.begin();
				bool finish = false;
				unsigned int lengthA=base->getWordLength();
				while((it2!=wrdsAux.end())&&(!finish)){
					if((lengthA+(*it2)->getWordLength())<length/2){
						lengthA+=(*it2)->getWordLength();
						wrds.insert(*it2);
						it2++;
					}
					else
						finish=true;
				}

				//Copio registro que apunta it en frontcoding mas chico
				if(lengthA>(length-lengthA-(*it2)->getWordLength())){
					//creo nuevo front coding con it2
					fcm2=new FrontCodingManager(*(*it2),0,NULL);
					delete (*it2);
				}
				else{
					//creo nuevo front coding con it2++
					wrds.insert(*it2);
					it2++;
					fcm2=new FrontCodingManager(*(*it2),0,NULL);
					delete (*it2);
				}

				it2++;
				//paso el resto de los registros al nuevo
				while(it2!=wrdsAux.end()){
					fcm2->insertWord(*(*it2));
					delete (*it2);
					it2++;
				}
				wrdsAux.clear();
			}
			return fcm2;
		}

		/*std::ostream& toOstream(std::ostream& out)const{
			out<<"Front Coding Content:"<<std::endl;
			out<<"\tword["<<0<<"]:"<<std::endl;
			base->toOstream(out);
			Words::iterator it = wrds.begin();
			unsigned int i=1;
			while(it!=wrds.end()){
				out<<"\tword["<<i<<"]:"<<std::endl;
				(*it)->toOstream(out);
				i++;
				it++;
			}
			return out;
		}*/
		
		std::ostream& toOstream(std::ostream& out)const{
			/*base->toOstream(out);
			Words::iterator it = wrds.begin();
			unsigned int i=1;
			while(it!=wrds.end()){
				out<<"\tword["<<i<<"]:"<<std::endl;
				(*it)->toOstream(out);
				i++;
				it++;
			}*/
			//Imprimo la primer palabra del registro

			out<<"\t\t------Palabra base del registro------"<<std::endl;
			out<<"\t\tId palabra: "<<base->getWordId()<<"\n";
			std::pair<unsigned int, char*> wrdBase=base->getWord();
			out<<"\t\tFC: ("<<wrdBase.first<<")";
			out<<"("<<0<<")";
			
			out<<"(";
			for(char* ss=wrdBase.second; ss<(wrdBase.second+wrdBase.first); ss++)
				out<<*ss;
			out<<")\n";
			

			BlockPointer* blocPoint=base->getBP();
			if(blocPoint==NULL)
				blocPoint=new BlockPointer(NOFILEPOINTER,NOFILEPOINTER);
			blocPoint->toOstream(out);
			delete blocPoint;


			//Imprimo el resto de las palabras
			out<<"\n\t\t------Palabras comprimidas con front coding------"<<std::endl;
			Words::iterator it = wrds.begin();
			
			BlockPointer blockPointerAux(0,0);

			char* lastWord=wrdBase.second;
			unsigned int lastWordLength=wrdBase.first;

			unsigned short int numberOfDifferentLetters;
			unsigned short int numberOfRepetedLetters;
			unsigned int idKey;

			//unsigned int totalLength=0;

			while(it!=wrds.end()){
				//Busco coincidencias entre las letras de la palabra nueva y la vieja
				std::pair<unsigned int,char*> newWrd = (*it)->getWord();
				unsigned int i=0;
				bool find=false;
				while((i<lastWordLength)&&(i<newWrd.first)&&(!find)){
					if(lastWord[i]==(newWrd.second)[i])
						i++;
					else
						find=true;
				}

				//Cargo variables con datos de la nueva palabra
				numberOfRepetedLetters=i;
				numberOfDifferentLetters=newWrd.first-i;
				idKey=(*it)->getWordId();
				delete[] lastWord;
				lastWord=newWrd.second;
				lastWordLength=newWrd.first;

				//imprimo datos palabra en vector datos
				out<<"\n\t\tId palabra: "<<idKey<<"\n";
				
				out<<"\t\tFC: ("<<numberOfDifferentLetters<<")";
				out<<"("<<numberOfRepetedLetters<<")";
				
				//Guardo la palabra recortada en vector lexico
				char* savedLexico=new char[numberOfDifferentLetters];
				memcpy(savedLexico, newWrd.second+numberOfRepetedLetters, numberOfDifferentLetters);
				out<<"(";
				for(char* ss=savedLexico; ss<(savedLexico+numberOfDifferentLetters); ss++)
					out<<*ss;
				out<<")\n";
				delete[] savedLexico;

				BlockPointer* blocPoint=(*it)->getBP();
				if(blocPoint==NULL)
					blocPoint=new BlockPointer(NOFILEPOINTER,NOFILEPOINTER);
				blocPoint->toOstream(out);
				delete blocPoint;

				it++;
			}
			delete[] lastWord;
			return out;
		}
		

		bool modifyWord(WordContainer& wc){
			bool ret = false;
			int comparate = wc.comparate(*base) ;
			if(comparate==0){
				base->setFields(wc);
				ret=true;
			}
			else{
				Words::iterator it = wrds.find(&wc);
				if(it!=wrds.end()){
					(*it)->setFields(wc);
					ret = true;
				}
			}
			return ret;
		}

};

}

#endif


